grammar Alkaline;

options {
	//language=JavaScript;
	output = AST;
}
tokens { BLOCK; EMPTY; LABEL; EXPR; ArgDecl; ITEM; SELECT; FILTER; SelectDescendants; CALL; OBJECT; ARRAY; ArrayComprehension; DESTRUCTURE; UNDEFINED; PostOp; XmlNodeList; XmlElement; XmlTextNode; XmlAttributes; XmlAttribute; TokenList; DefaultDirective; }
/*Java*/
@header {
	import java.util.Hashtable;
	import java.util.regex.Pattern;
}
/**/
@lexer::members {
	/*JS* /
	this.xmlDepth = 0;
	this.xmlDepthHistory = [];
	this.insideXml = false;
	this.insideXmlHistory = [];
	this.closingXmlTag = false;
	this.lastParenExpecting = [];
	/*Java*/
	int xmlDepth = 0;
	List<Integer> xmlDepthHistory = new ArrayList();
	boolean insideXml = false;
	List<Boolean> insideXmlHistory = new ArrayList();
	boolean closingXmlTag = false;
	Stack<Integer> lastParenExpecting = new Stack();
	/**/
	
	/**
	 * expecting values:
	 * 0 = item: regexp ("/asd/i"), paranthesized expression ("(1+2)"), array ("[1, 2]"), xml ("<asdf></asdf>"), etc
	 * 1 = function call ("sum(2,3)"), operator ("1/2") or index (a[3])
	 * 2 = operator ("false/2"), but no function call ("true(4)");
	 * 3 = paranthesized block with special meaning ("if (a)", "let (a = 2) {...}")
	 * 4 = function name (Id) or argument list ("function _a_() {...}" or "function _(_) {...}")
	 * 5 = getter/setter name or function call ("a = { get _prop_() { return "this._prop"; };" or "a.get_(_1);")
	 *
	 * -1 = none of the above
	 */
	/*JS* /
	this.expecting = 0;
	/*Java*/
	int expecting = 0;
	/**/
	
	/*Java*/
	String getLocation(int line, int pos) {
	/*JS* /
	this.getLocation = function(line, pos) {
		/**/
		return "line " + line + ", char " + pos;
	}
	
	/*JS* /
	this.getErrorHeader = function(e) {
	/*Java*/
	public String getErrorHeader(RecognitionException e) {
		/**/
		return this.getLocation(e.line, e.charPositionInLine+1);
	}
	
	/*JS* /
	this.addError = function(code, text, line, pos) {
	/*Java*/
	void addError(int code, String text, int line, int pos) {
		/**/
		this.state.syntaxErrors++;
		this.emitErrorMessage("SyntaxError E" + code + " at " + this.getLocation(line, pos) + ": " + text);
	}
	
	/*JS* /
	this.emitWarningMessage = function(msg) {
		if (typeof window != "undefined" && window.alert)
			alert(msg);
		else
			print(msg);
	}
	/*Java*/
	void emitWarningMessage(String msg) {
		System.out.println(msg);
	}
	/**/
	
	/*JS* /
	this.addWarning = function(code, text, line, pos) {
	/*Java*/
	void addWarning(int code, String text, int line, int pos) {
		/**/
		this.emitWarningMessage("Warning W" + code + " at " + this.getLocation(line, pos) + ": " + text);
	}
	
	/*JS* /
	this.pushXmlDepth = function() {
	/*Java*/
	void pushXmlDepth() {
		/**/
		this.xmlDepthHistory.add(this.xmlDepth);
		this.xmlDepth = 0;
		this.insideXmlHistory.add(this.insideXml);
		this.insideXml = false;
	}
	
	/*JS* /
	this.popXmlDepth = function() {
		var length;
	/*Java*/
	void popXmlDepth() {
		int length;
		/**/
		length = this.xmlDepthHistory.size();
		if (length == 0)
			this.xmlDepth = 0;
		else {
			length--;
			this.xmlDepth = this.xmlDepthHistory.get(length);
			this.xmlDepthHistory.remove(length);
			this.insideXml = this.insideXmlHistory.get(length);
			this.insideXmlHistory.remove(length);
		}
	}
	
	/*JS* /
	this.unescapeString = function(str) {
		var map = this.unescapeString.map;
		str = '"' + str.substr(1, str.length-2).replace(/\\\n\r?/g, '').replace(/\\(u....|x..|.)/g, function(_, x) {
			return map.hasOwnProperty(x) ? map[x] : _;
		}).replace(/\"/g, '\\\"') + '"';
		return str;
	}
	this.unescapeString.map = {};
	(function(){
		var map = this.unescapeString.map;
		var intervals = [['a', 'z'], ['A', 'Z'], ['0', '9'], ["'", "'"], ['"', '"']];
		for (var i = 0; i < intervals.length; i++) {
			var start = intervals[i][0].charCodeAt(0);
			var stop = intervals[i][1].charCodeAt(0);
			for (var j = start; j <= stop; j++) {
				var hex = j.toString(16);
				if (hex.length < 2) hex = '0' + hex;
				map['x' + hex] = 
				map['u00' + hex] = 
				map['\\' + String.fromCharCode(j)] =
					String.fromCharCode(j);
			}
		}
		var chars = [['\v', '\\v'], ['\t', '\\t'], ['\n', '\\n'], ['\r', '\\r'], ['\f', '\\f'], ['\b', '\\b']];
		for (var i = 0; i < chars.length; i++) {
			var hex = chars[i][0].charCodeAt(0).toString(16);
			if (hex.length < 2) hex = '0' + hex;
			map['x' + hex] =
			map['u00' + hex] =
			map[chars[i][0]] =
			map[chars[i][1]] =
				chars[i][1];
		}
		map['"'] = '"';
		map["'"] = "'";
	}).call(this);
	/*Java*/
	String unescapeString(String str) {
		return str;
	}
	/**/
}
@parser::members {
	/*JS* /
	this.strictMode = false;
	this.context = 0;
	this.contexts = new ArrayList();
	this.contextTypes = new ArrayList();
	this.contextDeclarations = new ArrayList();
	this.contextLabels = new ArrayList();
	this.assignedVariables = new ArrayList();
	this.assignedVariablesWhere = new ArrayList();
	this.assignedVariablesStrict = new ArrayList();
	this.assignedVariablesHere = new ArrayList();
	this.assignedVariablesHereWhere = new ArrayList();
	this.assignedVariablesHereStrict = new ArrayList();
	this.precedence = new Hashtable();
	this.markedSemi = -1;
	this.insideXmlSelector = false;
	this.functionDeclarations = [];
	this.insideFunction = 0;
	this.lastLineCommentIndex = -1;
	/**/
	/*Java*/
	boolean strictMode = false;
	int context = 0;
	List<List<String>> contexts = new ArrayList();
	List<String> contextTypes = new ArrayList();
	List<List<String>> contextDeclarations = new ArrayList();
	List<Hashtable> contextLabels = new ArrayList();
	List<List<String>> assignedVariables = new ArrayList();
	List<List<Tree>> assignedVariablesWhere = new ArrayList();
	List<List<Boolean>> assignedVariablesStrict = new ArrayList();
	List<String> assignedVariablesHere = new ArrayList();
	List<Tree> assignedVariablesHereWhere = new ArrayList();
	List<Boolean> assignedVariablesHereStrict = new ArrayList();
	static Hashtable<String, Integer> precedence = new Hashtable();
	int markedSemi = -1;
	boolean insideXmlSelector = false;
	List<CommonTree> functionDeclarations = new ArrayList();
	int insideFunction = 0;
	int lastLineCommentIndex = -1;
	static {
	/**/
		/*JS* /this./**/precedence.put("*", 5);
		/*JS* /this./**/precedence.put("/", 5);
		/*JS* /this./**/precedence.put("\u0025", 5);	// this is the percent operator (division remainder)
		/*JS* /this./**/precedence.put("+", 6);
		/*JS* /this./**/precedence.put("-", 6);
		/*JS* /this./**/precedence.put("<<", 7);
		/*JS* /this./**/precedence.put(">>", 7);
		/*JS* /this./**/precedence.put(">>>", 7);
		/*JS* /this./**/precedence.put("<", 8);
		/*JS* /this./**/precedence.put("<=", 8);
		/*JS* /this./**/precedence.put(">", 8);
		/*JS* /this./**/precedence.put(">=", 8);
		/*JS* /this./**/precedence.put("==", 9);
		/*JS* /this./**/precedence.put("===", 9);
		/*JS* /this./**/precedence.put("!=", 9);
		/*JS* /this./**/precedence.put("!==", 9);
		/*JS* /this./**/precedence.put("in", 9);
		/*JS* /this./**/precedence.put("instanceof", 9);
		/*JS* /this./**/precedence.put("&", 10);
		/*JS* /this./**/precedence.put("^", 11);
		/*JS* /this./**/precedence.put("|", 12);
		/*JS* /this./**/precedence.put("&&", 13);
		/*JS* /this./**/precedence.put("||", 14);
		/*JS* /this./**/precedence.put("?", 15);
	/*Java*/
	}
	/**/
	/*JS* /
	this.locationMessageSeparator =
	/*Java*/
	String locationMessageSeparator =
	/**/
		": ";
	
	/*JS* /
	this.getLocation = function(line, pos) {
	/*Java*/
	public String getLocation(int line, int pos) {
		/**/
		return "line " + line + ", char " + pos;
	}
	
	/*JS* /
	this.getErrorHeader = function(e) {
	/*Java*/
	public String getErrorHeader(RecognitionException e) {
		/**/
		return this.getLocation(e.line, e.charPositionInLine+1);
	}
	
	
	/*JS* /
	this.getCurrentHeader = function() {
	/*Java*/
	public String getCurrentHeader() {
		/**/
		try {
			throw new org.antlr.runtime.RecognitionException(this.input);
		} catch (/*Java*/RecognitionException/**/ re) {
			return this.getErrorHeader(re);
		}
	}
	
	
	/*JS* /
	this.getTokenLocation = function(t) {
	/*Java*/
	public String getTokenLocation(Token t) {
		/**/
		return this.getLocation(t.getLine(), t.getCharPositionInLine()+1);
	}
	
	
	/*JS* /
	this.getTreeLocation = function(t) {
	/*Java*/
	public String getTreeLocation(Tree t) {
		/**/
		return this.getLocation(t.getLine(), t.getCharPositionInLine()+1);
	}
	
	/*JS* /
	this.getFirstTreeToken = function(t) {
	/*Java*/
	public Token getFirstTreeToken(CommonTree t) {
		/**/
		while (t.getChildCount() > 0 && t.getCharPositionInLine() >= t.getChild(0).getCharPositionInLine() && (/*Java*/(CommonTree)/**/t.getChild(0)).getToken() != null)
			t = /*Java*/(CommonTree)/**/t.getChild(0);
		if (t instanceof org.antlr.runtime.tree.CommonErrorNode)
			return (/*Java*/(CommonErrorNode)/**/t).start;
		return t.getToken();
	}
	
	/*JS* /
	this.addError = function(code, error) {
		var errorType;
	/*Java*/
	public void addError(int code, String error) {
		String errorType;
		/**/
		this.state.syntaxErrors++;
		errorType = "Syntax";
		this.emitErrorMessage(errorType + "Error E" + code + " at " + error);
	}
	
	/*JS* /
	this.addErrorFromToken = function(code, error, token) {
	/*Java*/
	public void addErrorFromToken(int code, String error, Token token) {
		/**/
		this.addError(code, this.getTokenLocation(token) + this.locationMessageSeparator + error);
	}
	
	/*JS* /
	this.addErrorFromTree = function(code, error, tree) {
	/*Java*/
	public void addErrorFromTree(int code, String error, Tree tree) {
		/**/
		this.addError(code, this.getTreeLocation(tree) + this.locationMessageSeparator + error);
	}
	
	/*JS* /
	this.markSemi = function() {
	/*Java*/
	void markSemi() {
		/**/
		this.markedSemi = this.input.index();
	}
	
	/*JS* /
	this.MVSV = function(force, onlyIfNewLine) {
		// Make Virtual-Semi Visible
		var i, input, stream, tokens, semi, lastHiddenIdx, inserting, isMissing, warningToken, prevToken, decLocation;
	/*Java*/
	public boolean MVSV(boolean force, boolean onlyIfNewLine) {
		// Make Virtual-Semi Visible
		int i;
		TokenStream input;
		CommonTokenStream stream;
		List<Token> tokens;
		Token semi;
		int lastHiddenIdx;
		int inserting;
		boolean isMissing;
		Token warningToken;
		Token prevToken;
		boolean decLocation;
		/**/
		inserting = -1;
		isMissing = true;
		warningToken = null;
		prevToken = null;
		decLocation = false;
		input = this.input;
		i = input.index();
		if (i < input.size())
			if (input.get(i) == null)
				return false;
			else if (input.get(i).getType() == this.Semi)
				return true;
		i--;
		while (i > this.markedSemi && i > this.lastLineCommentIndex && (input.get(i).getChannel() == org.antlr.runtime.Token.HIDDEN_CHANNEL || input.get(i).getType() == this.LnCmnt)) {
			if (input.get(i).getType() == this.LnCmnt || input.get(i).getType() == this.Semi) {
				inserting = i+1;
				break;
			}
			i--;
		}
		if (i > -1 && i == this.lastLineCommentIndex)
			inserting = this.lastLineCommentIndex+1;
		if (inserting > -1) {
			lastHiddenIdx = inserting-1;
			while (lastHiddenIdx > 0 && input.get(lastHiddenIdx-1).getChannel() == org.antlr.runtime.Token.HIDDEN_CHANNEL || input.get(lastHiddenIdx-1).getType() == this.Comment)
				lastHiddenIdx--;
			if (lastHiddenIdx > 0)
				prevToken = input.get(lastHiddenIdx - 1);
			isMissing = prevToken == null || prevToken.getType() != this.CC && prevToken.getType() != this.Semi;
			if (isMissing) {
				warningToken = input.get(lastHiddenIdx);
				decLocation = true;
			}
		}
		if (inserting == -1 && !onlyIfNewLine) {
			if (i > this.markedSemi && input.get(i).getType() == this.Semi) {
				input.seek(i);
				return true;
			}
			i = input.index();
			if ((i == input.size()) ||	// at end
				(force && input.LA(1) != this.CC && input.LA(1) != this.Semi && (input.LA(-1) == this.CC || input.LA(-1) == this.CP))) {
				// insert semicolon at end
				inserting = i;
				isMissing = input.LA(-1) != this.CC && input.LA(-1) != this.Semi && (i == input.size() || input.LA(2) != this.CC);
			}
		}
		if (inserting > -1) {
			// insert semicolon at position inserting
			if (input instanceof org.antlr.runtime.CommonTokenStream)
				stream = /*Java*/(CommonTokenStream)/**/ input;
			else
				stream = /*Java*/(CommonTokenStream)/**/ ( /*Java*/(org.antlr.runtime.debug.DebugTokenStream)/**/input ).input;
			tokens = stream.getTokens();
			semi = new org.antlr.runtime.CommonToken(this.Semi, "");
			semi.setTokenIndex(inserting);
			if (inserting > 0) {
				semi.setLine(tokens.get(inserting-1).getLine());
				semi.setCharPositionInLine(tokens.get(inserting-1).getCharPositionInLine() + tokens.get(inserting-1).getText().length/*Java*/()/**/);
			}
			tokens.add(inserting, semi);
			input.seek(inserting);
			if (isMissing)
				if (decLocation)
					this.addWarning(3001, this.getLocation(warningToken.getLine(), warningToken.getCharPositionInLine()) + this.locationMessageSeparator + "Missing semicolon.");
				else
					this.addWarningFromToken(3001, "Missing semicolon.", warningToken == null ? semi : warningToken);
			return true;
		}
		return false;
	}
	
	/*JS* /
	this.emitWarningMessage = function(msg) {
		if (typeof window != "undefined" && window.alert)
			alert(msg);
		else
			print(msg);
	}
	/*Java*/
	void emitWarningMessage(String msg) {
		System.out.println(msg);
	}
	/**/

	/*JS* /
	this.addWarning = function(code, warn) {
	/*Java*/
	void addWarning(int code, String warn) {
		/**/
		this.emitWarningMessage("Warning W" + code + " at " + warn);
	}
	
	/*JS* /
	this.displayWarning = function(code, text) {
	/*Java*/
	void displayWarning(int code, String text) {
		/**/
		this.addWarning(code, this.getCurrentHeader() + this.locationMessageSeparator + text);
	}
	
	/*JS* /
	this.addWarningFromToken = function(code, warn, token) {
	/*Java*/
	void addWarningFromToken(int code, String warn, Token token) {
		/**/
		this.addWarning(code, this.getTokenLocation(token) + this.locationMessageSeparator + warn);
	}
	
	/*JS* /
	this.addWarningFromTree = function(code, warn, tree) {
	/*Java*/
	void addWarningFromTree(int code, String warn, Tree tree) {
		/**/
		this.addWarning(code, this.getTreeLocation(tree) + this.locationMessageSeparator + warn);
	}
	
	/*JS* /
	this.addStrictError = function(code, warn) {
	/*Java*/
	void addStrictError(int code, String warn) {
		/**/
		if (this.strictMode)
			this.addError(code, warn);
		else
			this.addWarning(code, warn);
	}
	
	/*JS* /
	this.addStrictErrorFromToken = function(code, warn, token) {
	/*Java*/
	void addStrictErrorFromToken(int code, String warn, Token token) {
		/**/
		this.addStrictError(code, this.getTokenLocation(token) + this.locationMessageSeparator + warn);
	}
	
	/*JS* /
	this.checkEnableStrictMode = function(str) {
	/*Java*/
	boolean checkEnableStrictMode(String str) {
		/**/
		if (this.strictMode) return false;
		if (str.substring(1, str.length/*Java*/()/**/-1).equals("use strict")) {
			this.strictMode = true;
			return true;
		}
		return false;
	}
	
	/*JS* /
	this.isStrictDeclaration = function(tree) {
	/*Java*/
	boolean isStrictDeclaration(CommonTree tree) {
		/**/
		do {
			if (tree == null || tree.getToken() == null || tree.getChildCount() != 1)
				return false;
			tree = /*Java*/(CommonTree)/**/ tree.getChild(0);
		} while (tree.getType() == this.EXPR || tree.getType() == this.ITEM);
		return tree.getType() == this.String && tree.getToken().getText().equals("\"use strict\"");
	}
	
	/*JS* /
	this.varIsDefined = function(id) {
		var i;
	/*Java*/
	boolean varIsDefined(String id) {
		int i;
		/**/
		for (i = this.context-1; i >= 0; i--)
			if (this.contexts.get(i).contains(id))
				return true;
		return false;
	}
	
	/*JS* /
	this.accessVariable = function(id) {
	/*Java*/
	void accessVariable(String id) {
		/**/
		if (id == null || id.equals("this"))
			return;
		if (!this.varIsDefined(id))
			this.displayWarning(3002, "Accessing undefined variable \"" + id + "\".");
	}
	
	/*JS* /
	this.pushContext = function(contextType) {
	/*Java*/
	void pushContext(String contextType) {
		/**/
		this.contexts.add(new ArrayList());
		this.contextTypes.add(contextType);
		this.contextDeclarations.add(new ArrayList());
		this.contextLabels.add(new Hashtable());
		
		this.assignedVariables.add(this.assignedVariablesHere);
		this.assignedVariablesWhere.add(this.assignedVariablesHereWhere);
		this.assignedVariablesStrict.add(this.assignedVariablesHereStrict);
		this.assignedVariablesHere = new ArrayList();
		this.assignedVariablesHereWhere = new ArrayList();
		this.assignedVariablesHereStrict = new ArrayList();
		
		this.context++;
	}
	
	/*JS* /
	this.popContext = function() {
	/*Java*/
	void popContext() {
		/**/
		this.context--;
		// skip context 0 because these are built-ins
		if (this.context > 0 && this.contexts.get(this.context).size() != this.contextDeclarations.get(this.context).size()) {
			this.state.syntaxErrors++;
			if (this.state.syntaxErrors == 1)	// do not show the warning if another error was already reported 
				System.out.println("Consistency error! Generated variable declarations for " +
					this.contextDeclarations.get(this.context).size() + " out of " + this.contexts.get(this.context).size() + " variables.\n" +
					"Generated for " + this.contextDeclarations.get(this.context) + ", total were " + this.contexts.get(this.context) + ".\nPosition: " + this.getCurrentHeader());
		}
		this.checkUnknownVariableAssignments();
		this.contexts.remove(this.context);
		this.contextTypes.remove(this.context);
		this.contextDeclarations.remove(this.context);
		this.contextLabels.remove(this.context);
		
		this.assignedVariablesHere.addAll(this.assignedVariables.remove(this.context));
		this.assignedVariablesHereWhere.addAll(this.assignedVariablesWhere.remove(this.context));
		this.assignedVariablesHereStrict.addAll(this.assignedVariablesStrict.remove(this.context));
		
		if (this.context == 0)
			this.printUndeclaredVariableAssignment();
	}
	
	/*JS* /
	this.addLabel = function(label, token) {
	/*Java*/
	void addLabel(String label, Token token) {
		/**/
		if (this.hasLabel(label) != null) {
			this.addErrorFromToken(1009, "The label \"" + label + "\" is already defined in the current context.", token);
			return;
		}
		this.contextLabels.get(this.context-1).put(label, true);
	}
	
	/*JS* /
	this.hasLabel = function(label) {
		var ctxIdx;
	/*Java*/
	Boolean hasLabel(String label) {
		int ctxIdx;
		/**/
		ctxIdx = this.context-1;
		while (ctxIdx > 0) {
			if (this.contextLabels.get(ctxIdx).containsKey(label))
				return /*Java*/(Boolean)/**/this.contextLabels.get(ctxIdx).get(label);
			else if (this.contextTypes.get(ctxIdx).equals("function"))
				ctxIdx = 0;
			else
				ctxIdx--;
		}
		return null;
	}

	/*JS* /
	this.checkLabel = function(label, token) {
		var check;
	/*Java*/
	void checkLabel(String label, Token token) {
		Boolean check;
		/**/
		check = this.hasLabel(label);
		if (check == null || check != true)
			this.addErrorFromToken(1008, "The label \"" + label + "\" does not exist, is not in the current context or cannot be used here.", token);
	}
	
	/*JS* /
	this.invalidateLabel = function(label) {
	/*Java*/
	void invalidateLabel(String label) {
		/**/
		if (!this.contextLabels.get(this.context-1).containsKey(label)) {
			this.state.syntaxErrors++;
			System.out.println("Consistency error! Cannot invalidate label \"" + label + "\".");
		} else
			this.contextLabels.get(this.context-1).put(label, false);
	}
	
	/*JS* /
	this.defineVar = function(id, contextType, implicitDefinition, token) {
		var context, i, lst;
	/*Java*/
	void defineVar(String id, String contextType, boolean implicitDefinition, Token token) {
		int context, i;
		List<String> lst;
		/**/
		if (token != null)
			if (id.equals("eval") || id.equals("arguments")) {
				this.addStrictErrorFromToken(2004, "The identifier \"" + id + "\" is reserved and cannot be used here.", token);
				if (this.strictMode)
					return;
			} else if (id.equals("this")) {
				this.addErrorFromToken(1012, "\"this\" is a keyword and cannot be used as a variable name.", token);
				return;
			}
		context = this.context-1;
		if (!contextType.equals(""))
			while (context > 1 && !(this.contextTypes.get(context).equals(contextType)))
				context--;
		if (contextType.equals("function arguments") && this.contexts.get(context).size() > 0) {
			lst = this.contexts.get(context);
			i = 0;
			if (!lst.get(0).equals("arguments")) i++;	// the function name is also in the list
			i++;	// skip "arguments"
			for (; i < lst.size(); i++)
				if (lst.get(i).equals(id)) {
					this.addStrictErrorFromToken(2005, "The argument \"" + id + "\" is already specified for this function.", token);
					return;
				}
		} else
			if (this.contexts.get(context).contains(id) || (context > 0 && this.contextTypes.get(context-1).equals("function arguments") && this.contexts.get(context-1).contains(id)))
				return;
		this.contexts.get(context).add(id);
		if (implicitDefinition)
			this.contextDeclarations.get(context).add(id);
	}
	
	/*JS* /
	this.defineBuiltInObjects = function() {
		var i;
		var builtIns = [
	/*Java*/
	void defineBuiltInObjects() {
		int i;
		String[] builtIns = {
		/**/
			"Object", "Function", "RegExp", "String", "Boolean", "Array", "Date",
			"Error", "EvalError", "RangeError", "ReferenceError", "SyntaxError", "TypeError", "URIError",
			"Math", "JSON", "eval",
			"parseInt", "parseFloat", "isNaN", "isFinite",
			"encodeURI", "decodeURI", "encodeURIComponent", "decodeURIComponent"
		/*JS* /]/*Java*/}/**/;
		for (i = 0; i < builtIns.length; i++)
			this.defineVar(builtIns[i], "", false, null);
	}
	
	/*JS* /
	this.checkVariableAssignment = function(id, location) {
	/*Java*/
	void checkVariableAssignment(String id, CommonTree location) {
		/**/
		if (id == null) return;
		if (id.equals("this")) {
			this.addErrorFromTree(1001, "\"this\" is a keyword and cannot be assigned.", location);
			return;
		}
		if (!this.varIsDefined(id)) {
			this.assignedVariablesHere.add(id);
			this.assignedVariablesHereWhere.add(location);
			this.assignedVariablesHereStrict.add(this.strictMode);
		}
	}
	
	/*JS* /
	this.checkUnknownVariableAssignments = function() {
		var lst, i, j, varName;
	/*Java*/
	void checkUnknownVariableAssignments() {
		List<String> lst;
		int i, j;
		String varName;
		/**/
		if (this.assignedVariablesHere.size() == 0)
			return;
		lst = this.contexts.get(this.context);
		for (i = lst.size()-1; i >= 0; i--) {
			varName = lst.get(i);
			for (j = this.assignedVariablesHere.size()-1; j >= 0; j--)
				if (this.assignedVariablesHere.get(j).equals(varName)) {
					this.assignedVariablesHere.remove(j);
					this.assignedVariablesHereWhere.remove(j);
					this.assignedVariablesHereStrict.remove(j);
				}
		}
	}
	
	/*JS* /
	this.printUndeclaredVariableAssignment = function() {
		var i, size, msg, tree;
	/*Java*/
	void printUndeclaredVariableAssignment() {
		int i, size;
		String msg;
		Tree tree;
		/**/
		size = this.assignedVariablesHere.size();
		for (i = 0; i < size; i++) {
			msg = "Assignment to undeclared variable \"" + this.assignedVariablesHere.get(i) + "\".";
			tree = this.assignedVariablesHereWhere.get(i);
			if (this.assignedVariablesHereStrict.get(i))
				this.addErrorFromTree(2001, msg, tree);
			else
				this.addWarningFromTree(2001, msg, tree);
		}
	}
	
	/*JS* /
	this.opIsAssignment = function(op) {
		var len;
	/*Java*/
	boolean opIsAssignment(String op) {
		int len;
		/**/
		if (op.equals("="))
			return true;
		len = op.length/*Java*/()/**/;
		if (len > 1 && op.substring(len-1, len).equals("=") && ("!=<>".indexOf(op.substring(0, 1)) < 0 || ("<>".indexOf(op.substring(0, 1)) > -1 && len > 2)))
			return true;
		return false;
	}
	
	/*JS* /
	this.getOpPriority = function(op) {
	/*Java*/
	int getOpPriority(String op) {
		/**/
		if (this.opIsAssignment(op))
			return  16;
		return this.precedence.get(op);
	}
	
	/*JS* /
	this.isAssignable = function(tree, insideItem) {
		var type;
	/*Java*/
	boolean isAssignable(CommonTree tree, boolean insideItem) {
		int type;
		/**/
		if (!insideItem) {
			if (tree.getType() != this.ITEM || tree.getChildCount() != 1)
				return false;
			tree = /*Java*/(CommonTree)/**/tree.getChild(0);
		}
		type = tree.getType();
		if (type == this.Id || type == this.SELECT || type == this.FILTER || type == this.SelectDescendants ||
			(!insideItem && type == this.ARRAY))
			return true;
		else
			return false;
	}
	
	/*JS* /
	this.checkOperation = function(opTree) {
		var lhs, tree, childTree, destructure, item, i, varName;
	/*Java*/
	void checkOperation(CommonTree opTree) {
		CommonTree lhs;
		CommonTree tree;
		Tree childTree;
		CommonTree destructure;
		CommonTree item;
		int i;
		String varName;
		/**/
		tree = lhs = /*Java*/(CommonTree)/**/opTree.getChild(0);
		if (tree.getType() == this.ITEM && tree.getChildCount() == 1)
			tree = /*Java*/(CommonTree)/**/tree.getChild(0);
		varName = (tree.getType() == this.Id) ? tree.getToken().getText() : null;
		if (this.opIsAssignment(opTree.getToken().getText()))
			if (!this.isAssignable(lhs, false)) {
				this.addErrorFromToken(1006, "This expression is not assignable.", this.getFirstTreeToken(lhs));
			} else if (varName != null) {
				if (lhs.getChildCount() == 1)
					this.checkVariableAssignment(varName, lhs);
			} else {
				if (tree.getType() == this.ARRAY) {
					// replace (EXPR (ARRAY id1 id2 etc)) with (DESTRUCTURE id1 id2 etc)
					destructure = /*Java*/(CommonTree)/**/this.adaptor.create(this.DESTRUCTURE, "DESTRUCTURE");
					for (i = 0; i < tree.getChildCount(); i++) {
						
						childTree = tree.getChild(i);
						if (childTree.getType() != this.EXPR || childTree.getChildCount() != 1) {
							this.addError(1002, this.getTreeLocation(childTree) + ": Invalid destructuring at item " + i + ".");
							continue;
						}
						childTree = childTree.getChild(0);
						if (childTree.getType() != this.ITEM || childTree.getChildCount() != 1) {
							this.addError(1002, this.getTreeLocation(childTree) + ": Invalid destructuring at item " + i + ".");
							continue;
						}
						childTree = childTree.getChild(0);
						if (childTree.getType() != this.Id) {
							this.addError(1002, this.getTreeLocation(childTree) + ": Invalid destructuring at item " + i + ".");
							continue;
						}
						item = /*Java*/(CommonTree)/**/this.adaptor.create(this.ITEM, "ITEM");	// TODO: the new ITEM token is missing line and char
						item.addChild(/*Java*/(CommonTree)/**/this.adaptor.create(this.Number, "" + i));
						item.addChild(childTree.dupNode());
						destructure.addChild(item);
					}
					this.adaptor.setChild(opTree, 0, destructure);
				}
			}
	}
	
	
	/*JS* /
	this.findPivot = function(operators, startIndex, stopIndex) {
		var pivot, pivotRank, i, op, current, rtl;
	/*Java*/
	int findPivot(List operators, int startIndex, int stopIndex) {
		int pivot, pivotRank, i, current;
		String op;
		boolean rtl;
		/**/
                pivot = startIndex;
                pivotRank = this.getOpPriority((/*Java*/(CommonTree)/**/operators.get(pivot)).getText());
                for (i = startIndex + 1; i <= stopIndex; i++) {
                        op = (/*Java*/(CommonTree)/**/operators.get(i)).getText();
                        current = this.getOpPriority(op);
                        rtl = !this.opIsAssignment(op);
                        if (current > pivotRank || (current == pivotRank && rtl)) {
                                pivot = i;
                                pivotRank = current;
                        }
                }
                return pivot;
        }
	
	/*JS* /
	this.makeExpressionTree = function(items, operators, startIndex, stopIndex) {
		if (arguments.length == 2) {
			startIndex = 0;
			stopIndex = items.length-1;
		}
		var pivot, root, root_1;
	/*Java*/
	CommonTree makeExpressionTree(List<CommonTree> items, List<CommonTree> operators) {
		return this.makeExpressionTree(items, operators, 0, items.size()-1);
	}
	CommonTree makeExpressionTree(List<CommonTree> items, List<CommonTree> operators, int startIndex, int stopIndex) {
		int pivot;
		CommonTree root;
		Object root_1;
		/**/
		if (stopIndex == startIndex)
			return items.get(startIndex);
		
		pivot = this.findPivot(operators, startIndex, stopIndex - 1);
		root = /*Java*/(CommonTree)/**/this.adaptor.nil();
		root_1 = /*Java*/(Object)/**/this.adaptor.nil();
		root_1 = /*Java*/(Object)/**/this.adaptor.becomeRoot(/*Java*/(CommonTree)/**/operators.get(pivot), root_1);
		this.adaptor.addChild(root_1, this.makeExpressionTree(items, operators, startIndex, pivot) );
		this.adaptor.addChild(root_1, this.makeExpressionTree(items, operators, pivot + 1, stopIndex));
		this.adaptor.addChild(root, root_1 );
		this.checkOperation(/*Java*/(CommonTree)/**/root_1);
		return root;
	}
	
	/*JS* /
	this.makeItemTree = function(opPre, item, tails, opPost) {
		var nextTail, preOps, tailsCount;
		var newItem;
		var checkedAssignment;
	/*Java*/
	CommonTree makeItemTree(CommonTree opPre, CommonTree item, List<CommonTree> tails, CommonTree opPost) {
		int nextTail, preOps, tailsCount;
		CommonTree newItem;
		boolean checkedAssignment;
		/**/
		checkedAssignment = item instanceof org.antlr.runtime.tree.CommonErrorNode;	// do not give more errors if we are recovering from an error in the first place
		if (opPre != null && !opPre.isNil()) {
			newItem = /*Java*/(CommonTree)/**/this.adaptor.nil();
			this.adaptor.addChild(newItem, opPre);
			opPre = newItem;
		} 
		nextTail = 0;
		preOps = (opPre == null) ? 0 : opPre.getChildCount();
		tailsCount = (tails == null) ? 0 : tails.size();
		if (preOps > 0 && opPre.getChild(preOps-1).getType() == this.New) {
			while (nextTail < tailsCount && (newItem = tails.get(nextTail)).getType() == this.SELECT) {
				if (newItem.getChildCount() > 0)
					this.adaptor.replaceChildren(newItem, 0, -1, item);
				else
					this.adaptor.addChild(newItem, item);
				item = newItem;
				nextTail++;
			}
			while (preOps > 0 && (newItem = /*Java*/(CommonTree)/**/opPre.getChild(preOps-1)).getType() == this.New) {
				this.adaptor.addChild(newItem, item);
				item = newItem;
				if (nextTail < tailsCount && (/*Java*/(CommonTree)/**/tails.get(nextTail)).getType() == this.CALL) {
					if (tails.get(nextTail).getChildCount() > 0)
						newItem.addChildren(tails.get(nextTail).getChildren());
					nextTail++;
				}
				preOps--;
			}
		}
		while (nextTail < tailsCount) {
			newItem = tails.get(nextTail);
			if (newItem.getChildCount() > 0)
				this.adaptor.replaceChildren(newItem, 0, -1, item);
			else
				this.adaptor.addChild(newItem, item);
			item = newItem;
			nextTail++;
		}
		if (opPost != null) {
			if (!checkedAssignment && !this.isAssignable(item, true)) {
				checkedAssignment = true;
				this.addErrorFromToken(1006, "This expression cannot be incremented or decremented.", this.getFirstTreeToken(item));
			}
			newItem = /*Java*/(CommonTree)/**/this.adaptor.create(this.PostOp, "PostOp");
			this.adaptor.addChild(newItem, item);
			this.adaptor.addChild(newItem, opPost);
			item = newItem;
		}
		while (preOps > 0) {
			preOps--;
			newItem = /*Java*/(CommonTree)/**/opPre.getChild(preOps);
			this.adaptor.addChild(newItem, item);
			if (newItem.getType() == this.PrePostOperator) {
				newItem.getToken().setType(this.UnaryOperator);
				if (!checkedAssignment && !this.isAssignable(item, true)) {
					checkedAssignment = true;
					this.addErrorFromToken(1006, "This expression cannot be incremented or decremented.", this.getFirstTreeToken(item));
				}
			} else if (newItem.getType() == this.Delete) {
				if (item.getType() == this.Id)
					this.addStrictErrorFromToken(2007, "\"delete\" operator used on a direct reference to a variable, function argument, or function name.", item.getToken());
			}
			item = newItem;
		}
		return item;
	}
	
	/*JS* /
	this.makeVarDeclList = function() {
		var tree, i, count, ctx, alreadyDeclared, name;
	/*Java*/
	CommonTree makeVarDeclList() {
		CommonTree tree;
		int i, count;
		List<String> ctx;
		List<String> alreadyDeclared;
		String name;
		/**/
		ctx = this.contexts.get(this.context-1);
		count = ctx.size();
		tree = /*Java*/(CommonTree)/**/this.adaptor.nil();
		alreadyDeclared = this.contextDeclarations.get(this.context-1);
		for (i = 0; i < count; i++) {
			name = ctx.get(i);
			if (!alreadyDeclared.contains(name)) {
				this.adaptor.addChild(tree, this.adaptor.create(this.Id, name));
				alreadyDeclared.add(name);
			}
		}
		return tree;
	}
	
	/*JS* /
	this.makeVarDeclTree = function() {
		var tree, i, count, ctx, alreadyDeclared, name;
	/*Java*/
	CommonTree makeVarDeclTree() {
		CommonTree tree;
		int i, count;
		List<String> ctx;
		List<String> alreadyDeclared;
		String name;
		/**/
		ctx = this.contexts.get(this.context-1);
		count = ctx.size();
		alreadyDeclared = this.contextDeclarations.get(this.context-1);
		if (count == alreadyDeclared.size())
			return /*Java*/(CommonTree)/**/this.adaptor.nil();
		if (this.contextTypes.get(this.context-1).equals("global") || this.contextTypes.get(this.context-1).equals("function"))
			tree = /*Java*/(CommonTree)/**/this.adaptor.create(this.Var, "var");
		else
			tree = /*Java*/(CommonTree)/**/this.adaptor.create(this.Let, "let");
		for (i = 0; i < count; i++) {
			name = ctx.get(i);
			if (!alreadyDeclared.contains(name)) {
				this.adaptor.addChild(tree, this.adaptor.create(this.Id, name));
				alreadyDeclared.add(name);
			}
		}
		return tree;
	}
	
	/*JS* /
	this.checkString = function(str, token) {
		if (/\\(0[0-9]|[1-9])/.test(str.replace(/\\\\/g, '')))
	/*Java*/
	void checkString(String str, Token token) {
		if (Pattern.compile("\\\\(0[0-9]|[1-9])").matcher(str.replace("\\\\", "")).find())
		/**/
			this.addStrictErrorFromToken(2006, "Octal escapes are not allowed in strings in strict mode.", token);
	}
	
	/*JS* /
	this.processFunctionDeclarations = function(start, stop) {
		var i, ctxIdx, nameIdx, tree, funTree, op, name, ctx;
		if (start == null) start = 0;
		if (stop == null) stop = this.functionDeclarations.length-1;
	/*Java*/
	void processFunctionDeclarations() {
		this.processFunctionDeclarations(0);
	}
	void processFunctionDeclarations(int start) {
		this.processFunctionDeclarations(start, this.functionDeclarations.size()-1);
	}
	void processFunctionDeclarations(int start, int stop) {
		int i, ctxIdx;
		String name;
		List<String> ctx;
		CommonTree tree, funTree, op;
		/**/
		for (i = stop; i >= start; i--) {
			funTree = this.functionDeclarations.get(i);
			if (funTree.getParent().getParent() != null || this.context != 2) {
				this.functionDeclarations.remove(i);
				tree = /*Java*/(CommonTree)/**/this.adaptor.create(this.EXPR, "EXPR");
				op = /*Java*/(CommonTree)/**/this.adaptor.create(this.Operator, "=");
				tree.addChild(op);
				op.addChild(funTree.getChild(0).dupNode());
				funTree.getParent().replaceChildren(funTree.getChildIndex(), funTree.getChildIndex(), tree);
				op.addChild(funTree);
				name = funTree.getChild(0).getText();
				ctxIdx = this.context-1;
				while (ctxIdx >= 0) {
					ctx = this.contextDeclarations.get(ctxIdx);
					if (ctx.contains(name)) {
						ctx.remove(ctx.indexOf(name));
						break;
					}
					ctxIdx--;
				}
			}
		}
	}
	
	/*JS* /
	this.endsInExpression = function(tree) {
		var type;
	/*Java*/
	boolean endsInExpression(CommonTree tree) {
		int type;
		/**/
		while (tree != null && tree.getToken() != null) {
			type = tree.getToken().getType();
			if (type == this.If || type == this.For || type == this.While || type == this.With)
				tree = /*Java*/(CommonTree)/**/tree.getChild(tree.getChildCount()-1);
			else if (type == this.DefaultDirective || type == this.EXPR || type == this.Return || type == this.Throw)
				return true;
			else
				return false;
		}
		return false;	// fallback; whouldn't actually be used unless there were parsing errors
	}
	
	/*JS* /
	this.getCommentStartIdx = function() {
		var startIdx;
	/*Java*/
	int getCommentStartIdx() {
		int startIdx;
		/**/
		startIdx = this.input.index();
		while (startIdx > 0 && this.input.get(startIdx-1).getChannel() == org.antlr.runtime.Token.HIDDEN_CHANNEL)
			startIdx--;
		return startIdx;
	}

	/*JS* /
	this.collectComments = function(start, tree, untilEnd) {
		var newToken, stop;
	/*Java*/
	int collectComments(int start, CommonTree tree, Boolean untilEnd) {
		Token newToken;
		int stop;
		/**/
		stop = this.input.index();
		if (!untilEnd && (stop == this.input.size() || this.input.get(stop).getType() != this.CC))
			while (stop > start && this.input.get(stop-1).getChannel() == org.antlr.runtime.Token.HIDDEN_CHANNEL)
				stop--;
		while (start < stop) {
			newToken = this.input.get(start);
			if (newToken.getType() == this.LnCmnt || (newToken.getType() == this.Comment && newToken.getChannel() == org.antlr.runtime.Token.HIDDEN_CHANNEL)) {
				newToken.setType(this.Comment);
				newToken.setChannel(org.antlr.runtime.Token.DEFAULT_CHANNEL);
				tree.addChild(/*Java*/(CommonTree)/**/ this.adaptor.create(newToken));
				this.lastLineCommentIndex = start;
			}
			start++;
		}
		return start;
	}
	
	/*JS* /
	this.addInfoToComments = function(start, text) {
		var newToken, stop;
		stop = this.input.index();
		if (stop == this.input.size() || this.input.get(stop).getType() != this.CC)
			while (stop > start && this.input.get(stop-1).getChannel() == org.antlr.runtime.Token.HIDDEN_CHANNEL)
				stop--;
		while (start < stop) {
			newToken = this.input.get(start);
			if (newToken.getType() == this.LnCmnt || (newToken.getType() == this.Comment && newToken.getChannel() == org.antlr.runtime.Token.HIDDEN_CHANNEL)) {
				if (!newToken.additionalCommentInfo)
					newToken.additionalCommentInfo = [ text ];
				else
					newToken.additionalCommentInfo.push(text);
			}
			start++;
		}
		return start;
	}
	/*Java*/
	int addInfoToComments(int start, String text) {
		return start;
	}
	/**/
	
	/*JS* /
	this.tokenToStringTree = function(token, prefix) {
		token.setText('"' + (prefix || '') + token.getText() + '"');
		token.setType(this.String);
		return this.adaptor.create(token);
	}
	/*Java*/
	CommonTree tokenToStringTree(Token token) {
		token.setText('"' + token.getText() + '"');
		token.setType(this.String);
		return (CommonTree) this.adaptor.create(token);
	}
	
	CommonTree tokenToStringTree(Token token, String prefix) {
		token.setText('"' + prefix + token.getText() + '"');
		token.setType(this.String);
		return (CommonTree) this.adaptor.create(token);
	}
	/**/
	
	/*JS* /
	this.tokenToSelect = function(token) {
	/*Java*/
	Token tokenToSelect(Token token) {
		/**/
		token.setText("SELECT");
		token.setType(this.SELECT);
		return token;
	}
}



//
//	Parser
//



prog
@init {
	this.pushContext("built-in");
	this.defineBuiltInObjects();
	this.pushContext("global");
	/**
	var lt = this.input.LT;
	var oThis = this;
	this.input.LT = function() {
		var t = lt.apply(oThis.input, arguments);
		if (t)
			echo(t.line + ':' + t.charPositionInLine);
		else
			echo(typeof t);
		return t;
	}
	/**/
}
@after {
	this.popContext();
	this.popContext();
	//if ($tree != null) System.out.println((/*Java*/(Tree)/**/$tree).toStringTree());
}
	:	{
			if (this.input.LA(1) == this.String)
				this.checkEnableStrictMode(this.input.LT(1).getText());
		}
		blockContent[true] EOF!;

semicolon
	:	{ this.markSemi(); } Semi;

blockContent[/*Java*/boolean/**/ collectVariableDeclarations]
@init {
	/*JS* /var/*Java*/CommonTree/**/ varDeclTree = /*Java*/(CommonTree)/**/ this.adaptor.nil();
	/*JS* /var/*Java*/int/**/ funDeclStart = this.functionDeclarations.size();
	/*JS* /var/*Java*/CommonTree/**/ currentBlock = /*Java*/(CommonTree)/**/ this.adaptor.nil();
	/*JS* /var/*Java*/CommonTree/**/ strictSwitch = /*Java*/(CommonTree)/**/ this.adaptor.nil();
	/*JS* /var/*Java*/int/**/ startIdx = this.getCommentStartIdx();
}	:
		(
			( lastI=instructionOrBlock {
				startIdx = this.collectComments(startIdx, currentBlock, false);
				
				if (collectVariableDeclarations && this.isStrictDeclaration(/*Java*/(CommonTree)/**/ $lastI.tree))
					strictSwitch =/*Java*/(CommonTree)/**/  $lastI.tree;
				else
					currentBlock.addChild(/*Java*/(CommonTree)/**/ $lastI.tree);
				this.MVSV(true, false);
			} )?
			(
				semicolon
				( lastI=instructionOrBlock {
					startIdx = this.collectComments(startIdx, currentBlock, false);
					
					currentBlock.addChild(/*Java*/(CommonTree)/**/ $lastI.tree);
					this.MVSV(true, false);
				})?
			)*
		)
		{
			startIdx = this.collectComments(startIdx, currentBlock, true);
			
			if (collectVariableDeclarations) {
				this.processFunctionDeclarations(funDeclStart);
				if (this.contexts.get(this.context-1).size() > 0)
					varDeclTree = this.makeVarDeclTree();
			}
		}
		-> ^(BLOCK {strictSwitch} {varDeclTree} {currentBlock});

instructionOrBlock
	:	(Id Colon { this.addLabel($Id.text, $Id); })?
		((OC)=>block | (Function)=>func[true] | instructionBody)
		{ if ($Id != null) { this.invalidateLabel($Id.text); } }
		-> {$Id != null}? ^(LABEL Id block? func? instructionBody?)
		-> block? func? instructionBody?;
instructionSemiOrBlock returns[boolean endsInExpression]
@init {
	$endsInExpression = false;
	/*JS* /var/*Java*/CommonTree/**/ t = null;
}	:	(Id Colon { this.addLabel($Id.text, $Id); })?
		
		(	(OC)=>block
		|	(Function)=>func[true]
		|	fakeEmptyBlock
		|	instructionBody
			{ $endsInExpression = this.endsInExpression(/*Java*/(CommonTree)/**/$instructionBody.tree); }
		)
		
		{ if ($Id != null) { this.invalidateLabel($Id.text); } }
		-> {$Id != null}? ^(LABEL Id block? func? fakeEmptyBlock? instructionBody?)
		-> block? func? fakeEmptyBlock? instructionBody?;
instructionBody
	:	expressionsBlock | cinstr;
cinstr	:	ifExpr | whileExp | doExpr | forExpr
	|	flowCtrl | variableDeclaration | retStmt | withExpr | tryExpr | switchStmt | letStmt | throwStmt | defaultDirective
	|	Debugger;
expressionsBlock
	:	expressions -> ^(EXPR expressions);
expressions
	:	expression (Comma! expression)*;

spexpr	:	OPS! expressionsBlock CP!;
expression
@init {
	/*JS* /var/*Java*/List<CommonTree>/**/ items = new ArrayList();
	/*JS* /var/*Java*/List<CommonTree>/**/ ops = new ArrayList();
	/*JS* /var/*Java*/CommonTree/**/ exprTree = null;
	/*JS* /var/*Java*/CommonTree/**/ newTree = null;
	/*JS* /var/*Java*/CommonTree/**/ destructure = null;
	/*JS* /var/*Java*/CommonTree/**/ qMarkTree = null;
	/*JS* /var/*Java*/CommonTree/**/ childTree = null;
	/*JS* /var/*Java*/CommonTree/**/ lastAssignmentTree = null;
}
	:	item=itemExpr { items.add(/*Java*/(CommonTree)/**/$item.tree); }
		(
			opMid {
				newTree = /*Java*/(CommonTree)/**/ $opMid.tree;
				ops.add(newTree);
				if (this.opIsAssignment(newTree.getToken().getText()))
					lastAssignmentTree = newTree;
			}
			item=itemExpr { items.add(/*Java*/(CommonTree)/**/$item.tree); }
		)*
		{ exprTree = this.makeExpressionTree(items, ops); }
		(-> ^(EXPR {exprTree}))
		(
			QMark { $QMark.setType(this.Operator); }
			tru=expression Colon fals=expression
			{
				qMarkTree = /*Java*/(CommonTree)/**/this.adaptor.create(/*Java*/(CommonToken)/**/$QMark);
				if (lastAssignmentTree != null) {
					qMarkTree.addChild(lastAssignmentTree.getChild(1));
					lastAssignmentTree.replaceChildren(1, 1, qMarkTree);
					childTree = /*Java*/(CommonTree)/**/$tree;
				} else {
					qMarkTree.addChild((/*Java*/(CommonTree)/**/exprTree));
					childTree = qMarkTree;
				}
				
				qMarkTree.addChild((/*Java*/(CommonTree)/**/$tru.tree));
				qMarkTree.addChild((/*Java*/(CommonTree)/**/$fals.tree));
			}
			-> ^(EXPR {childTree})
		)?;

block
@init {
	this.pushContext("block");
}
@after {
	this.popContext();
	/*JS* /var/*Java*/CommonTree/**/ tree = /*Java*/(CommonTree)/**/$tree;
	tree.getToken().setLine($oc.getLine());
	tree.getToken().setCharPositionInLine($oc.getCharPositionInLine());
}
	:	oc=OC! blockContent[true] CC!;
functionBody	// this is basically a block without the lexical context (so that blockContent will know it is inside a function)
	:	OC!
		{ this.insideFunction++; }
		blockContent[true]
		{ this.insideFunction--; }
		CC!;

fakeEmptyBlock
	:	( Semi | {this.input.mark();} CB {this.input.rewind();} )	// requires an explicit semicolon unless inside array comprehension
		-> ^(BLOCK);

ifExpr
@init {
	/*JS* /var/*Java*/boolean/**/ hasSemi = false;
	/*JS* /var/*Java*/boolean/**/ reqSemi;
	/*JS* /var/*Java*/boolean/**/ hasElse;
}	:	If^ spexpr then=instructionSemiOrBlock
		(
			(Semi Else)=>
			Semi! { hasSemi = true; }
		)?
		{
			hasElse = this.input.LA(1) == this.Else;
			reqSemi = $then.endsInExpression && hasElse;
			if (reqSemi && !hasSemi) {
				//this.input.mark();
				if (this.MVSV(true, true))
					this.input.consume();
				else
					this.addErrorFromToken(1013, "A semicolon was expected but not found", this.input.LT(1));
				//this.input.rewind();
			} else if (hasElse && hasSemi && (this.input.LA(-2) == this.Semi || (!$then.endsInExpression && this.input.LA(-2) == this.CC)))
				this.addErrorFromToken(1014, "An extra semicolon was encountered", $Semi);
		}
		((Else)=>Else! ( instructionOrBlock | fakeEmptyBlock ))?;
whileExp:	While^ spexpr ( instructionOrBlock | fakeEmptyBlock );
doExpr
@init {
	/*JS* /var/*Java*/boolean/**/ hasSemi = false;
}	:	Do^ action=instructionSemiOrBlock
		(Semi! { hasSemi = true; })?
		{
			if ($action.endsInExpression) {
				if (!hasSemi)
					this.addErrorFromToken(1013, "A semicolon was expected but not found", this.input.LT(1));
			} else if (hasSemi && (this.input.LA(-2) == this.CC || this.input.LA(-2) == this.Semi)) {
				this.addErrorFromToken(1014, "An extra semicolon was encountered", $Semi);
			}
		}
		While! spexpr;
withExpr:	With^ {
			if (this.strictMode)
				this.addErrorFromToken(2002, "\"with\" statements are not allowed in strict mode", $With);
			else
				this.addWarningFromToken(2002, "\"with\" statements are not allowed in strict mode", $With);
		} spexpr ( instructionOrBlock | fakeEmptyBlock );
forExpr	:	forStmtEach | forStmtClassic;
forStmtEach
	:	For^ Each OPS! forCondIn CP! ( instructionOrBlock | fakeEmptyBlock );
forStmtClassic
	:	For^ OPS! ( (Var? Let? Id In)=> forCondIn | forCondE ) CP! ( instructionOrBlock | fakeEmptyBlock );
forCondE:	(e1=exprsOrVarDecl -> $e1 | -> EMPTY) Semi
		(e2=expressionsBlock -> $forCondE $e2 | -> $forCondE True) Semi
		(e3=expressionsBlock -> $forCondE $e3 | -> $forCondE EMPTY);
forCondIn
@init {
	/*JS* /var/*Java*/CommonTree/**/ letTree = /*Java*/(CommonTree)/**/this.adaptor.nil();
	/*JS* /var/*Java*/String/**/ varContext = null;
}
	:	(varKw { varContext = $varKw.context; })?
		Id
		{
			if (varContext != null)
				if (varContext.equals(""))
					letTree = /*Java*/(CommonTree)/**/$varKw.tree;
				else
					this.defineVar($Id.text, varContext, false, $Id);
			this.checkVariableAssignment($Id.text, /*Java*/(CommonTree)/**/this.adaptor.create($Id));
		}
		In expression
	->	^(In {letTree} Id expression);
tryExpr
@init {
	/*JS* /var/*Java*/int/**/ parts = 0;
}	:	Try^ block
		(condCatch { parts++; })*
		(catchStmt { parts++; })?
		(finallyStmt { parts++; })?
		{
			if (parts == 0)
				this.addErrorFromToken(1009, "\"try\" statement must be followed by \"catch\" or \"finally\" clause.", $Try);
		}
		;
condCatch
@init {
	this.pushContext("catch");
}
@after {
	this.popContext();
}
	:	Catch^ OPS! Id If! expression CP! { this.defineVar($Id.text, "catch", true, $Id); } block;
catchStmt
@init {
	this.pushContext("catch");
}
@after {
	this.popContext();
}
	:	Catch^ OPS! Id CP! { this.defineVar($Id.text, "catch", true, $Id); } block;
finallyStmt
	:	Finally^ block;
switchStmt
	:	Switch^ OPS! expression CP! OC! swCase* CC!;
swCase	:	(Case^ expression | Default^) Colon! blockContent[false];

flowCtrl:	flowControlKw^ (Id { this.checkLabel($Id.text, $Id); })?;
flowControlKw
	/*
		For some reason if we include flowControlKw inside flowCtrl then ANTLR JS module generates:
			set114=input.LT(1);
			set114=this.input.LT(1);
		and the first line crashes. "Outsourcing" this prevents the problem
	*/
	:	Break | Continue;
retStmt
@after {
	if (this.insideFunction < 1)
		this.addErrorFromTree(1010, "<return> encountered but not inside a function.", /*Java*/(Tree)/**/$tree);
}	:	Return^ expressionsBlock?;
retStmtImplied
	:	expression {this.MVSV(false, false);} {this.input.mark();} (Semi | Comma | CP) {this.input.rewind();} -> ^(BLOCK ^(Return expression));


variableDeclaration
@after {
	if ($tree != null && (/*Java*/(Tree)/**/$tree).getChildCount() > 1) {
		/*JS* /var/*Java*/Object/**/ newTree = this.adaptor.create(this.EXPR, "EXPR");
		this.adaptor.addChild(newTree, $tree);
		$tree = newTree;
	}
}	:	varKw!
		varDeclA[$varKw.context] ((Comma)=>(Comma! varDeclA[$varKw.context]))*;
varDeclA[/*Java*/String/**/ context]
@init {
	/*JS* /var/*Java*/Tree/**/ exprTree = /*Java*/(Tree)/**/this.adaptor.nil();
}	:	kwIdItem
		(Equal expression { exprTree = (/*Java*/(Tree)/**/$expression.tree).getChild(0); } -> ^(EXPR ^(Operator[$Equal] {$kwIdItem.tree} {exprTree})))?
		{
			/*JS* /var/*Java*/Token/**/ token = (/*Java*/(CommonTree)/**/$kwIdItem.tree).token;
			if (token != null)
				this.defineVar(token.getText(), $context, false, token);
		};
exprsOrVarDecl
@init {
	/*JS* /var/*Java*/CommonTree/**/ declTree = /*Java*/(CommonTree)/**/this.adaptor.nil();
}
@after {
	if ($tree == null)
		$tree = this.adaptor.create(this.EMPTY, "EMPTY");
}
	:	expressionsBlock
	|
		{ this.pushContext("block"); }
		variableDeclaration
		{ declTree = this.makeVarDeclTree(); }
		{ this.popContext(); }
		-> {declTree} ^(EXPR variableDeclaration)?;
letStmt
@init {
	this.pushContext("down");
}
@after {
	this.popContext();
}
	:	Let OPS
		(varDeclA["down"] (Comma varDeclA["down"])*)?
		CP
		instructionOrBlock
	->	^(Let { this.makeVarDeclList() } varDeclA* instructionOrBlock);

throwStmt
	:	Throw^ expressionsBlock;

func[/*Java*/boolean/**/ isDeclaration]	// a function declaration or a function expression
@init {
	this.pushContext("function arguments");
	/*JS* /var/*Java*/boolean/**/ notStrictOutside = false;
	/*JS* /var/*Java*/String/**/ fname = null;
	/*JS* /var/*Java*/Token/**/ token;
}
@after {
	this.popContext();
	if (isDeclaration)
		this.functionDeclarations.add(/*Java*/(CommonTree)/**/$tree);
}
	:	Function^
		(
			kwId { token = (/*Java*/(CommonTree)/**/$kwId.tree).getToken(); this.defineVar(fname = token.getText(), isDeclaration ? "function" : "function arguments", true, token); }
		|
			{ if ($isDeclaration) this.addErrorFromToken(1004, "Function declaration is missing the name of the function", $Function); }
		)
		{ this.defineVar("arguments", "function arguments", true, null); }
		argdecl
		{
			if (!this.strictMode && this.input.LA(1) == this.OC && this.input.LA(2) == this.String)
				if (this.checkEnableStrictMode(this.input.LT(2).getText()))
					notStrictOutside = true;
			this.pushContext("function");
		}
		((OC)=>functionBody | retStmtImplied)
		{
			this.popContext();
			if (notStrictOutside)
				this.strictMode = false;
		}
	;
argdecl:	(OPS | CALLop)
		(oneArg
		(Comma oneArg)*)?
		CP
	->	^(ArgDecl oneArg*);

oneArg
@init {
	/*JS* /var/*Java*/Token/**/ token;
}	:	kwIdItem
		{
			token = (/*Java*/(CommonTree)/**/$kwIdItem.tree).getToken();
			this.defineVar(token.getText(), "function arguments", true, token);
		};

// $<item

itemExpr
@init {
	/*JS* /var/*Java*/boolean/**/ canHavePostOp = true;
	/*JS* /var/*Java*/CommonTree/**/ itemTree = null;	
}
	:	opPre {canHavePostOp = $opPre.canHavePostOp; }
		itemBase tails+=itemTail*
		({canHavePostOp}? opPost)?
		{
			itemTree = this.makeItemTree(
				/*Java*/(CommonTree)/**/$opPre.tree,
				/*Java*/(CommonTree)/**/$itemBase.tree,
				/*Java*/(List<CommonTree>)/**/$tails,
				/*Java*/(CommonTree)/**/$opPost.tree);
		}
		-> ^(ITEM[$itemBase.start, "ITEM"] {itemTree});

itemBase
@after {
	/*JS* /var/*Java*/Tree/**/ t = /*Java*/(Tree)/**/$tree;
	/*JS* /var/*Java*/Tree/**/ t1;
	while (t.getType() == this.EXPR && t.getChildCount() == 1) {
		t1 = /*Java*/(Tree)/**/t.getChild(0);
		if (t1.getType() != this.ITEM)
			t = t1;
		else
			if (t1.getChild(0).getType() == this.Id)
				t = t1.getChild(0);
			else
				break;	// while
	}
	$tree = t;
}	:	(At {this.insideXmlSelector}? )? kwIdItem | literal | (OPE | CALLop)! expressionsBlock CP!;

itemTail
@init {
	/*JS* /var/*Java*/boolean/**/ wasInsideBefore = false;
	/*JS* /var/*Java*/Object/**/ propName = null;
}
	:	Dot
		(	Id { propName = this.tokenToStringTree($Id); }
		|	Number { propName = this.tokenToStringTree($Number); })
		->	^({this.tokenToSelect($Dot)} {propName})
	|	Dot Operator { $Operator.text.equals("*") }? -> ^({this.tokenToSelect($Dot)} {this.tokenToStringTree($Operator)})
	|	Dot kwX -> ^({this.tokenToSelect($Dot)} String["\"" + (/*Java*/(CommonTree)/**/$kwX.tree).getToken().getText() + "\""])
	|	firstDot=Dot Dot
			(	Id { propName = this.tokenToStringTree($Id); }
			|	Operator { $Operator.text.equals("*") }? { propName = this.tokenToStringTree($Operator); })
		-> ^(SelectDescendants[$firstDot, "SelectDescendants"] {propName})
	|	Dot At
		(	Id { propName = this.tokenToStringTree($Id, "@"); }
		|	Operator { $Operator.text.equals("*") }? { propName = this.tokenToStringTree($Operator, "@"); })
		-> ^({this.tokenToSelect($Dot)} {propName})
	|	OB expression CB -> ^({this.tokenToSelect($OB)} expression)
	|	(CALLop | OPE) expressions? CP -> ^(CALL[$CALLop != null ? $CALLop : $OPE, "CALL"] expressions?)
	|	Dot OPS {
			wasInsideBefore = this.insideXmlSelector;
			this.insideXmlSelector = true;
		} expression {
			this.insideXmlSelector = wasInsideBefore;
		} CP -> ^(FILTER[$Dot, "FILTER"] expression)
	;
// $>
// $<operators

opPre returns[boolean canHavePostOp]
@init {
	$canHavePostOp = true;
}
	:	UnaryOperator*
		(
			PrePostOperator
			{ $canHavePostOp = false; }
		)?
		(
			lastCmd=operPre
			{ if (!lastCmd.canHavePostOp) $canHavePostOp = false; }
		)*;
operPre returns[boolean canHavePostOp]
@init {
	$canHavePostOp = true;
}
	:	Yield | New { $canHavePostOp = false; } | Delete | TypeOf | Void;
opMid	:	operator | opInstanceOf | opIn;
opInstanceOf
	:	InstanceOf { $InstanceOf.setType(this.Operator); };
opIn	:	In { $In.setType(this.Operator); };
opPost	:	PrePostOperator;
operator:	Operator | Equal { $Equal.setType(this.Operator); };

// $>
// $<literals

literal	:	number | String { this.checkString($String.text, $String); } | bool | Null | array | func[false] | RegExp | xmlLiteral | object;
number	:	Number
		{
			if ($Number.text.length/*Java*/()/**/ > 1 && $Number.text.charAt(0) == '0' && $Number.text.charAt(1) >= '0' && $Number.text.charAt(1) <= '9')
				this.addStrictErrorFromToken(2006, "Octal numbers are not allowed in strict mode.", $Number);
		};
array
@init {
	/*JS* /var/*Java*/Token/**/ lastComma = null;
}
@after {
	/*JS* /var/*Java*/Tree/**/ tree;
	/*JS* /var/*Java*/int/**/ idx;
	tree = (/*Java*/(Tree)/**/$tree);
	idx = tree.getChildCount()-1;
	if (tree.getChild(idx).getType() == this.UNDEFINED) {
		tree.deleteChild(idx);
		if (tree.getChildCount() > 0)
			this.addWarningFromToken(3003, "Comma at the end of array literal", lastComma);
	}
}
	:	OB (arrayI
			(
				forExpr
				{
					/*JS* /var/*Java*/Tree/**/ last = /*Java*/(Tree)/**/ $forExpr.tree;
					while (last.getChildCount() > 0) {
						last = /*Java*/(Tree)/**/ last.getChild(last.getChildCount()-1);
						if (last.getType() == this.BLOCK && last.getLine() > -1) {
							this.addErrorFromTree(1011, "Blocks of code cannot be used inside array comprehension.", last);
							break;
						}
					}
				}
				->
				^(ArrayComprehension[$OB, "ArrayComprehension"] arrayI forExpr)
			|
				(Comma { lastComma = $Comma; } arrayI)*
				->
				^(ARRAY[$OB, "ARRAY"] arrayI*)
		)) CB;
arrayI	:	expression | -> UNDEFINED;
object
scope {
	List<String> properties;
	List<String> getters;
	List<String> setters;
	int commentStartIdx;
}
@after {
	/*JS* /var/*Java*/Token/**/ nextToken = this.input.LT(1);
	// when "'' + {} + 3" is lexed, the second "+" is considered a unary op. fix this
	if (nextToken != null && nextToken.getType() == this.UnaryOperator && (nextToken.getText().equals("+") || nextToken.getText().equals("-")))
		nextToken.setType(this.Operator);
}
	:	OC
		{
			$object::properties = /*JS* /[]/*Java*/new ArrayList()/**/;
			$object::getters = /*JS* /[]/*Java*/new ArrayList()/**/;
			$object::setters = /*JS* /[]/*Java*/new ArrayList()/**/;
			$object::commentStartIdx = this.getCommentStartIdx();
		}
		(objectPart (Comma objectPart)*)?
		(danglingComma=Comma { this.addWarningFromToken(3003, "Comma at the end of object literal", $danglingComma); })?
		CC
	->	^(OBJECT[$OC, "OBJECT"] objectPart*);
objectPart
@init {
	/*JS* /var/*Java*/String/**/ errMsg = null;
	/*JS* /var/*Java*/String/**/ errMsg2 = null;
	/*JS* /var/*Java*/boolean/**/ isGetter = false;
	/*JS* /var/*Java*/boolean/**/ strictErr = false;
	/*JS* /var/*Java*/CommonTree/**/ errTree = null;
	/*JS* /var/*Java*/String/**/ name = null;
}
@after {
	if (errMsg != null)
		if (!strictErr || this.strictMode)
			this.addErrorFromTree(2003, errMsg, errTree);
		else
			this.addWarningFromTree(2003, errMsg, errTree);
	if (errMsg2 != null)
		this.addErrorFromTree(1007, errMsg2, errTree);
	if (name != null) {
		$object::commentStartIdx = this.addInfoToComments($object::commentStartIdx, "object part name: " + name);
	} else
		$object::commentStartIdx = this.getCommentStartIdx();
}
	:	namedPart^ Colon! expression
		{
			name = (/*Java*/(CommonTree)/**/$namedPart.tree).getToken().getText();
			if ($object::properties.contains(name)) {
				errMsg = "The property \"" + name + "\" is already defined for this object.";
				strictErr = true;
			} else if ($object::getters.contains(name))
				errMsg = "A getter with the name \"" + name + "\" is already defined for this object.";
			else if ($object::setters.contains(name))
				errMsg = "A setter with the name \"" + name + "\" is already defined for this object.";
			if (errMsg == null)
				$object::properties.add(name);
			else
				errTree = /*Java*/(CommonTree)/**/ $namedPart.tree;
		}
	|
		{ this.pushContext("function arguments"); }
		(Set^ | Get^ { isGetter = true; })
		namedPart argdecl functionBody
		{
			name = (/*Java*/(CommonTree)/**/$namedPart.tree).getToken().getText();
			this.popContext();
			if ($object::properties.contains(name))
				errMsg = "A property with the name \"" + name + "\" is already defined for this object.";
			else if (isGetter && $object::getters.contains(name)) {
				errMsg = "The getter \"" + name + "\" is already defined for this object.";
				strictErr = true;
			} else if (!isGetter && $object::setters.contains(name)) {
				errMsg = "The setter \"" + name + "\" is already defined for this object.";
				strictErr = true;
			}
			if (errMsg == null)
				if (isGetter)
					$object::getters.add(name);
				else
					$object::setters.add(name);
			
			if (isGetter && (/*Java*/(Tree)/**/$argdecl.tree).getChildCount() > 0)
				errMsg2 = "Getters must not have any arguments.";
			else if (!isGetter && (/*Java*/(Tree)/**/$argdecl.tree).getChildCount() != 1)
				errMsg2 = "Setters must take one and only one argument.";
			
			if (errMsg != null || errMsg2 != null)
				errTree = /*Java*/(CommonTree)/**/ $namedPart.tree;
		};
namedPart
@init {
	/*JS* /var/*Java*/CommonToken/**/ token;
}	:	kwX {
			token = /*Java*/(CommonToken)/**/(/*Java*/(CommonTree)/**/$kwX.tree).getToken();
			token.setType(this.String);
			token.setText('"' + token.getText() + '"');
		}
	|	Id {
			$Id.setType(this.String);
			$Id.setText('"' + $Id.getText() + '"');
		}
	|	Number {
			$Number.setType(this.String);
			$Number.setText('"' + $Number.getText() + '"');
		}
	|	String;

bool	:	False | True;

defaultDirective
@after {
	if ($clss != null && !$clss.getText().equals("xml"))
		this.addErrorFromToken(1015, "The only default directive allowed is \"default xml namespace = ...\".", $clss);
	else if ($prop != null && !$prop.getText().equals("namespace"))
		this.addErrorFromToken(1015, "The only default directive allowed is \"default xml namespace = ...\".", $prop);
	$def.setType(this.DefaultDirective);
}	:	def=Default^ clss=Id prop=Id Equal! expressionsBlock;

// $>

// $<xml

xmlLiteral
	:	xmlNodeList | xmlElement | XmlCData;
xmlNodeList
	:	XmlLT XmlGT xmlNodeContent* XmlLT XmlSlash XmlGT -> ^(XmlNodeList xmlNodeContent*);
xmlNodeContent
	:	xmlElement | XmlCData | xmlTextNode | xmlDynamicNode;
xmlElement
@after {
	if ($tagStart.tagName != null && $tagEnd.tagName != null && !$tagStart.tagName.equals($tagEnd.tagName)) {
		this.addError(1003, this.getTreeLocation(/*Java*/(Tree)/**/$tagEnd.tree) + ": End tag \"" + $tagEnd.tagName + "\" does not match start tag \"" + $tagStart.tagName + "\" (" +
			this.getTreeLocation(/*Java*/(Tree)/**/$tagStart.tree) + ").");
	}
}
	:	XmlLT tagStart=xmlQName
		xmlAttribute*
		(
			XmlSlash XmlGT
			-> ^(XmlElement $tagStart xmlAttribute*)
		|
			XmlGT (extraNodes+=xmlNodeContent)* (XmlLT XmlSlash tagEnd=xmlQName XmlGT)
			-> ^(XmlElement ^(TokenList $tagStart $tagEnd) xmlAttribute* $extraNodes*)
		);
xmlAttribute
	:	(Id | attrName=xmlScript)
		Equal
		(String | attrValue=xmlScript)
	-> ^(XmlAttribute Id? $attrName? String? $attrValue?)
	;
xmlQName returns[String tagName]
	:	(i1=Id { $tagName = $i1.text; } -> Id) (Colon i2=Id { $tagName += ":" + $i2.text; } -> Id[$i1, $i1.text + ":" + $i2.text])?
	|	xmlScript { $tagName = null; };
xmlScript
	:	OC expression CC -> ^(BLOCK expression);

xmlTextNode
@init {
	/*JS* /var/*Java*/String/**/ nodeValue = "";
	/*JS* /var/*Java*/Token/**/ firstToken = null;
}
@after {
	$tree = /*Java*/(Tree)/**/ this.adaptor.create(this.XmlTextNode, firstToken, nodeValue);;
	this.adaptor.setTokenBoundaries($tree, $start, $stop);
}
	:	(
			~(XmlLT | XmlCData | OC)
			{
				nodeValue += this.input.LT(-1).getText();
				if (firstToken == null)
					firstToken = this.input.LT(-1);
			}
		)+
	;

xmlDynamicNode
	:	xmlScript;

// $>

varKw returns[String context]
	:	Var { $context = "function"; } | Let { $context = ""; } | Const { $context = "function"; };
kw	:	Null | False | True
	|	New | Delete | TypeOf | InstanceOf
	|	Continue | Break
	|	If | Else | Do | While | With | For | In
	|	Var | Let | Const | Return | Yield
	|	Try | Catch | Throw
	|	Switch | Case | Default
	|	kwItem;
kwItem	:	Each
	|	Get | Set;

kwX	:	Function | Debugger | Finally | Void
	|	kw;
	
kwId	:	Id
	|	kw { (/*Java*/(CommonTree)/**/$kw.tree).getToken().setType(this.Id); };

kwIdItem
	:	Id
	|	kwItem { (/*Java*/(CommonTree)/**/$kwItem.tree).getToken().setType(this.Id); };

//
//	Lexer
//



// special

// $<Punctuation

Dot	:	'.' {this.expecting = -1;};
Semi	:	';' {this.expecting = 0;};
Comma	:	',' {this.expecting = 0;};
Colon	:	':' {this.expecting = 0;};

// $>
// $<Parantheses

// function call:
CALLop	:	{expecting == 1 || expecting == 5}? '(' { this.lastParenExpecting.push(this.expecting); this.expecting = 0; };
// paranthesised expression:
OPE	:	{expecting == 0}? '(' { this.lastParenExpecting.push(this.expecting); this.expecting = 0; };
// special:
OPS	:	{expecting == 3 || expecting == -1 || expecting == 4 || expecting == 2}? '(' { this.lastParenExpecting.push(this.expecting); this.expecting = 0; };
CP
@init {
	/*JS* /var/*Java*/int/**/ lastExpecting = -2;
	if (!this.lastParenExpecting.empty())
		lastExpecting = this.lastParenExpecting.pop();
}
	:	')' { this.expecting = (lastExpecting == 3 || lastExpecting == 4) ? 0 : 1; };
OB	:	'[' {this.expecting = 0;};
CB	:	']' {this.expecting = 1;};
OC
@after {
	this.pushXmlDepth();
}
	:	'{' {this.expecting = 0;};

QMark	:	'?' {this.expecting = 0;};
CC
@after {
	this.popXmlDepth();
}
	:	'}' { this.expecting = this.lastParenExpecting.empty() ? 0 : 1;};

// $>

Equal	:	'=' {this.expecting = 0;};
At	:	'@' {this.expecting = 0;};

// $<Keywords
Null	:	'null'
		{
			// same as for "Id"
			if (this.expecting == 5)
				this.expecting = 4;	// after a getter/setter expect a list of arguments
			else if (this.expecting != 4)
				this.expecting = 1;
		};
//Undef	:	'undefined'	{this.expecting = -1;};	// no point in making this a special variable 
Function:	'function'	{this.expecting =  4;};
False	:	'false'		{this.expecting =  2;};
True	:	'true'		{this.expecting =  2;};
New	:	'new'		{this.expecting =  0;};
Delete	:	'delete'	{this.expecting =  0;};
TypeOf	:	'typeof'	{this.expecting =  0;};
InstanceOf
	:	'instanceof'	{this.expecting =  0;};
Continue:	'continue'	{this.expecting = -1;};
Break	:	'break'		{this.expecting = -1;};
If	:	'if'		{this.expecting =  3;};
Else	:	'else'		{this.expecting =  0;};
Do	:	'do'		{this.expecting =  0;};
While	:	'while'		{this.expecting =  3;};
For	:	'for'		{this.expecting =  3;};
Each	:	'each'		{ if (this.expecting <= 0) this.expecting = 1; };
In	:	'in'		{this.expecting =  0;};
With	:	'with'		{this.expecting =  3;};
Var	:	'var'		{this.expecting = -1;};
Let	:	'let'		{this.expecting =  3;};
Const	:	'const'		{this.expecting = -1;};
Return	:	'return'	{this.expecting =  0;};
Yield	:	'yield'		{this.expecting =  0;};
Try	:	'try'		{this.expecting = -1;};
Catch	:	'catch'		{this.expecting = -1;};
Finally	:	'finally'	{this.expecting = -1;};
Throw	:	'throw'		{this.expecting =  0;};
Switch	:	'switch'	{this.expecting =  3;};
Case	:	'case'		{this.expecting =  0;};
Default	:	'default'	{this.expecting =  0;};
Void	:	'void'		{this.expecting =  0;};
Set	:	'set'		{ if (this.expecting != 4) this.expecting = 5; };
Get	:	'get'		{ if (this.expecting != 4) this.expecting = 5; };

Debugger:	'debugger'	{this.expecting = -1;};

// $>

RegExp
@init {
	/*JS* /var/*Java*/boolean/**/ showErr = true;
	/*JS* /var/*Java*/boolean/**/ flagG = false;
	/*JS* /var/*Java*/boolean/**/ flagI = false;
	/*JS* /var/*Java*/boolean/**/ flagM = false;
	/*JS* /var/*Java*/String/**/ errMsg;
}
	:	{
			if (this.expecting != 0) {
				this.mOperator(); return;
			} else if (this.xmlDepth > 0) {
				this.mXmlSlash(); return;
			}
		}
		{ expecting == 0 && xmlDepth == 0 }?
		'/' RegExpItem+ '/'
		(
			'a'..'z'
			{
				errMsg = null;
				if (this.input.LA(-1) == 'g')
					if (!flagG)
						flagG = true;
					else
						errMsg = "The flag \"g\" has already been specified for this RegExp.";
				else if (this.input.LA(-1) == 'i')
					if (!flagI)
						flagI = true;
					else
						errMsg = "The flag \"i\" has already been specified for this RegExp.";
				else if (this.input.LA(-1) == 'm')
					if (!flagM)
						flagM = true;
					else
						errMsg = "The flag \"m\" has already been specified for this RegExp.";
				else
					errMsg = "This is not a valid character for RegExp flags.";
				if (errMsg != null && showErr) {
					this.addError(1005, errMsg, this.input.getLine(), this.input.getCharPositionInLine());
					showErr = false;	// show only the first bad flag
				}
			}
		)*
		{ this.expecting = 1; }
	;
fragment
RegExpItem
	:	(RXEscapeSequence | RegExpGroup | RegExpClass | ~('\\'|'/'|'\r'|'\n'|'['|'('|')'|'*'|'+'|'?')) RegExpMultiplier?;
fragment
RegExpMultiplier
	:	'?' | '+' '?'? | '*' '?'?;
fragment
RegExpGroup
	:	'(' ('?:'|'?='|'?!')? RegExpItem* ')';
fragment
RegExpClass
	:	'[' (RXEscapeSequence | ~('\\'|'\r'|'\n'|']'))* ']';
fragment
RXEscapeSequence
	:	'\\' ~('u')
	|	UEscape;

// $<Id

Id
@after {
	/*JS* /
	if (Alkaline.JSInfo.futureKeywordsHash.hasOwnProperty($text))
		this.addWarning(3005, "Future reserved keywords cannot be used as identifiers.", this.input.getLine(), this.input.getCharPositionInLine() - ($text).length + 1);
	/**/
}	:	IdentChar0 IdentChar*
		{
			if (this.expecting == 5)
				this.expecting = 4;	// after a getter/setter expect a list of arguments
			else if (this.expecting != 4)
				this.expecting = 1;
		};
fragment
IdentChar0
	:	'a'..'z' | 'A'..'Z' | '_' | '$';
fragment
IdentChar
	:	IdentChar0 | Digit;

// $>

XmlLT
@init {
	if (this.input.LA(2) == '!' && this.input.LA(3) == '[') {
		this.mXmlCData();
		return;
	}
}
@after {
	if (this.input.LA(1) != '/')
		this.xmlDepth++;
	this.insideXml = false;
}
	:	{ (expecting == 0 || xmlDepth > 0) }? '<' { this.expecting = 0; };
XmlGT
@after {
	if (this.closingXmlTag || this.input.LA(-2) == '/') {
		this.xmlDepth--;
		this.closingXmlTag = false;
	}
	if (this.xmlDepth > 0)
		this.insideXml = true;
}
	:	{ (xmlDepth > 0) }? '>';
XmlSlash
@after {
	if (this.input.LA(-2) == '<')
		this.closingXmlTag = true;
}
	:	{ (xmlDepth > 0) }? '/';

XmlCData
@init {
}
	:	'<![CDATA[' (~']' | ']' ~']' | ']]' ~'>')* ']]>';

// $<Operators

Operator
@init {
	/*Java*/
	// needed for gunit to test Operator
	if (state.tokenStartCharIndex == -1)
		state.tokenStartCharIndex = 0;
	/**/
	if (this.xmlDepth > 0)
		if (this.input.LA(1) == '>') {
			this.mXmlGT();
			return;
		} else if (this.input.LA(1) == '<') {
			this.mXmlLT();
			return;
		} else if (this.input.LA(1) == '/') {
			this.mXmlSlash();
			return;
		}
	if (this.expecting <= 0 || this.expecting >= 3)
		if (this.input.LA(1) == '<') {
			this.mXmlLT();
			return;
		} else if (this.input.LA(1) == '/') {
			this.mRegExp();
			return;
		} else if (this.input.LA(1) == '-' || this.input.LA(1) == '+') {
			this.mUnaryOperator();
			return;
		}
}
	: SimpleOperator '='? {this.expecting = 0;};
	
fragment
SimpleOperator
	:	ComparisonOperator | BitwiseOperator | BooleanOperator | ArithmeticOperator;
fragment
ComparisonOperator
	:	'!=' | '==' | '<' '<'? | '>' ('>' '>'?)?;
fragment
BitwiseOperator
	:	'&' | '|' | '^';
fragment
BooleanOperator
	:	'&&' | '||';
fragment
ArithmeticOperator
	:	'+' | '-' | '*' | '/' | '%';

UnaryOperator
@init {
	if (this.expecting > 0 && this.expecting < 3)
		if (this.input.LT(1) == '~' || (this.input.LT(1) == '!' && this.input.LT(2) != '=')) {
			// "expecting" is incorrect
			this.expecting = 0;
		} else {
			this.mOperator();
			return;
		}
}
	:	('!' | '~' | '+' | '-');
PrePostOperator
	:	'++' | '--';

// $>

// $<Strings

String
@after {
	this.setText(this.unescapeString($text));
}	:	'"'  (EscapeSequence | LineContinuation | BadEscape | ~('\\'|'\"'|'\n'))* '"'	{this.expecting = 1;}
	|	'\'' (EscapeSequence | LineContinuation | BadEscape | ~('\\'|'\''|'\n'))* '\''	{this.expecting = 1;}
	;

// escapes

fragment
LineContinuation
	:	'\\' '\r'? '\n';

fragment
EscapeSequence
	:	'\\' EscapeChar
	|	UEscape
	|	OEscape
	|	HexEscape
	;

fragment
EscapeChar
	:	'b' | 't' | 'n' | 'v' | 'f' | 'r' | '\"' | '\'' | '\\';

fragment
NonEscapeChar
	:	~( EscapeChar | '\r' | '\n' | 'u' | 'x' | '0'..'7' );

fragment
OEscape	:	'\\' ('0'..'3') ('0'..'7') ('0'..'7')
	|	'\\' ('0'..'7') ('0'..'7')
	|	'\\' ('0'..'7')
	;

fragment
HexEscape
	:	'\\' 'x' HexDigit HexDigit;

fragment
UEscape	:	'\\' 'u' HexDigit HexDigit HexDigit HexDigit;

fragment
BadEscape
	:	'\\' NonEscapeChar;

// $>
// $<Numbers

Number	:	(Hex | Octal | Decimal | FloatingNr ExpoPart?) {this.expecting = 2;};
fragment
Hex	:	'0' ('x'|'X') HexDigit+;
fragment
Octal	:	'0' '0'..'7'+;
fragment
Decimal	:	('0' | '1'..'9' Digit*) ('.' Digit*)? ExpoPart?;
fragment
FloatingNr
	:	'.' Digit+;
fragment
ExpoPart:	('e' | 'E') ('+' | '-')? Digit+;

fragment
Digit	:	'0'..'9';

fragment
HexDigit:	Digit | 'a'..'f' | 'A'..'F';

// $>
// $<Hidden

WS	:   (' '|'\t'|'\r'|'\n')+
		{
			if (!this.insideXml)
				$channel=HIDDEN;
			if ($text.indexOf("\n") > -1 || $text.indexOf("\r") > -1)
				$type = this.Semi;
		};
Comment	:	'/*' ( options {greedy=false;} : . )* '*/'
		{
			if (!this.insideXml) {
				$channel=HIDDEN;
				/*JS* /var/*Java*/String/**/ str = $text;
				if (str.charAt(2) == '@' && str.charAt(str.length/*Java*/()/**/-3) == '@')
					this.addWarning(3004, "Conditional compiles are not supported.", $line, $pos);
			}
		};
LnCmnt
@after {
	if (!this.insideXml) {
		/*JS* /var/*Java*/String/**/ txt = $text;
		/*JS* /var/*Java*/int/**/ end = txt.length/*Java*/()/**/;
		if (txt.charAt(end-1) == '\n') {
			end--;
			if (txt.charAt(end-1) == '\r')
				end--;
			this.setText(txt.substring(0, end));
		}
	}
}	:	'//' ~('\n'|'\r')* '\r'? '\n'?
		{
			if (!this.insideXml)
				$channel = HIDDEN;
		};

// $>
